话说我在面试的时候,有那么几天,不知道是中了什么邪,面试的几家公司开始疯狂的问我this,
各种的this,绕着弯的问我this,后来我做梦都是this、this、this……你妹的this!
那么先从面试官会怎么问你来说吧。
面试官会问什么样的问题呢
- 请说一下作用域和上下文(this)
- this会指向哪里
- 如何改变this的指向
- 出各种绕弯子的题目让你说this是啥
其实这个虽然题目我记得不多了,但是真的问起来还是很恶心的。
因为一旦代码丢出来了,还会涉及到继承,构造函数,原型链,闭包等一系列问题在后面等着你,面试管为了掏你的底细会一问再问,问到你懵逼。
那么我们先来说说第一个问题,作用域和上下文。
什么是作用域?
问得好,我也不知道,待我查一下………………
作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。
emmmm…………不好理解的话,我说一个比喻吧。
现在我为一个房子专门定一个木窗窗沿,这个窗沿只适合这个房子里面所有需要用的地方,其他房子不能用,那么这个房子就是我木窗的作用域~~~
就像我在
function 房子(){
var 木窗;
}
我的木窗只属于我的房子,我不可以在房子的外面,例如小区里面直接拿到我的木窗,我必须进入房子去找这个木窗,这是不可以改变的作用域。
然后我们再来看一下
var 景色 = '大海'
function 房子() {
var 景色 = '大草原';
this.木窗 = function() {
console.log(景色)
}
木窗()
}
房子()
请问这个时候输出的景色是什么呢~
机智的小朋友肯定会说是大草原~~~
为什么呢~
因为景色我从下往上找啊,找到最近的一张图我就不用继续找了,拿出来用就好啦。
怎么样,惊不惊喜,意不意外,刺不刺激~~~
js中有全局作用域,函数作用域,块级作用域(es6)。
- 全局作用域
很好解释,我所有地方都可以调用的到的,就像路边的广告牌,我们都可以看得到,不需要想办法进谁家里去看。
- 函数作用域
是产生在函数中的,一个函数内部会出现一块作用域。可以这么理解,函数是我们的房子,我们站在房子外面(全局)的时候,无法看到房子里面的东西。但是我们站在房子里面(函数内部),是可以去透过窗子凑凑整个外面的世界的。
- 块级作用域
哇塞这个就厉害了,在我们es6里面,新增了一些像let、const之类的语法,可以产生一个块级作用域。
大家应该都做过一个简单的题目,就是一个for循环里面丢一个setTimeout,下面放出代码。
for(var i = 0; i<5; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
我们看一下这一个题目,首先会让你回答打印出来的是什么。
机智的小伙伴肯定会回答:5 5 5 5 5
为什么咧,因为这个var的i成了全局的,并不是只在循环里面去使用,setTimeout是一个异步函数,所以我们执行完了for才会去console.log。
这个还涉及到同步异步之类的,会单独开一章来讲,光是基础都已经让人头昏眼花,啊西吧~
那如果我们要让他12345怎么办呢,可以把i保留下来做一个闭包传入,还有一个最方便的办法就是用let来声明他。
ps: 不过一般面试官会跟你说不要用es6,给我一个es5的办法2333333333
=3=
另外说一下let和const和var主要的区别:
- 如果你要在块级声明的变量存在全局变量,但是块级作用域内又let或者const了一个局部变量,导致后者绑定这个块级作用域,就会…boom…爆炸,也就是报错~
- var大家都知道,进行变量提升,你在第十行声明赋值,我们代码执行的时候其实是会放到最顶部先声明,再执行到第十行的位置进行复制,还没赋值前都是undefind。但是let和const没有哦,如果你提前实用的话就等着报错吧~
- const声明一个只读的常量。一旦声明,常量的值就不能改变。但是呢,你可以改变他的属性。
就像
const i = 10;
i = 9; // 这样是不行的!!!
const u = {a: 'a',b: 'b'}
u.b = 'c' // 这样是可以的
其实很好理解,不过我还是举个?吧……
我const一个大爷,我改变这个大爷的属性,类似给他换个衣服或者换个鞋子,可是大爷还是大爷所以不会报错。
但是我const一个妹子,完事了把妹子改成了大爷,那我不依,报错。
那什么是上下文呢?
这个也好理解,比如我窗户装在客厅,看到的是客厅里面的景色,装到了厨房,就是厨房的景色,当然这个上下文是可以更改的,我可以个这个窗沿里面贴上珠穆拉玛峰的照片,那么这个上下文就被我改了。
function 房子() {
var 景色 = '大草原';
this.木窗 = function() {
console.log(this.景色)
}
木窗()
}
房子()
你猜打印出来的是什么~~~
当然是undefind!
想什么呢~不会以为是大草原吧~
为什么咧,因为他的this变了。
下面我把这行代码改为直接打印this,打印出来的是一个window。
为什么?因为我调用房子的地方在window下,我调用了房子,房子调用了木窗,所以this成了window。
那么我们刚刚是不是在房子里面给了一张大草原的画呢,这是不是在房子外面(window层)就找不到了呢,所以是个window。
哈哈哈哈哈
如果你在这里就昏了,那么一定要往下看,不然你永远都搞不清楚this到底会指向哪里。
再说一下第三个问题,改变this的指向。
这个其实也很简单,call或者apply都可以,这个自己看api去~考官会问你两个的区别的,乖宝宝要自己看文档哦~
最后一题,绕着弯子让你说this是啥。
既然你看到了这里,那么正题开始了!!!
到底this指向哪里!!!
问得好,我自己也懵逼。
没关系,我们可以一起捋一捋。
容老夫先沐浴更衣,焚香祈祷你们不要被我带歪了。
首先,跟我一起读一遍下面的两句话(当然,如果有补充可以留言):
1: 谁调用指向谁,没有谁就是window2: 除了call、apply、bind和箭头函数
首先我们在全局下打印一下this,控制台输出的是window,这个没有疑问吧~
我们再来通过函数调用一下
function 房子() {
this.木窗 = function() {
console.log(this)
}
木窗()
}
房子()
那么我们再回过头去看刚刚的房子窗子。
我们是哪里开始调用房子的?
当然是window,其实房子()就等同于window.房子()。
所以呢,调用房子的是window,那么房子最底下会调用木窗,所以真正的调用者被抓出来了。
window => 房子 => 木窗
函数的调用者就是this,请抓住始作俑者,这道题就是window这个小婊渣,就是他~
那么我们再看下一个。
通过对象的属性来调用:
var obj = {
say: function() {
console.log(this);
}
};
obj.say();
打印的结果是: {say: ƒ}
谁调用就指向谁,这个obj.say()的执行方法调用者是前面的obj,所以当前的这个this指向了obj。
再看一个
var obj = {
say: function() {
var hehe = function() {
console.log(this)
}
hehe()
}
};
obj.say();
这个this指向了window。
为什么?因为这个hehe并不是obj上面的属性,没有找到调用者是谁,所以默认指向window。
下面我们来看一下其余几个指向谁。
call和apply大家都知道,可以改变this的指向。
var obj = {a: 'a'};
function b() {
console.log(this)
}
b.apply(obj)
打印结果:{a: "a"}
call差不多,两者差别只是参数传的不一样,一个可以传数组。
具体情况,宝宝们文档走一波~
然后再来看一下bind
哇塞这个也厉害了
直接上代码
var axiba = {hehe: 'hehe'}
var obj = {
a: 'a',
b:function() {
console.log(this)
}.bind(axiba)
};
b()
// 或者
var axiba = {hehe: 'hehe'}
var obj = {
a: 'a',
b:function() {
console.log(this)
}
};
var u = obj.b.bind(axiba)
u()
执行的是神马~
当然是指向axiba~
最后一个,就是es6的箭头函数
我要单独为箭头函数加个粗
官网逛一逛,发现了几句话。
箭头函数有几个使用注意点。(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
要理解其实也很容易,首先……
上代码……
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。
所以以后有人问你es6的箭头函数的this和es5函数中的this的区别,那么就是es6不可改变,始终指向定义的对象,es5是根据调用环境的,如果没有调用者,默认window。
因为考官会出得题目千千万,题海战术几乎无用,我之前也是作用域和上下文傻傻分不清,虽然现在也懵懂阶段,不过学无止尽嘛。
有什么表达不当的地方,悄悄告诉我哦~
么么哒
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。